package com.frank.practice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.frank.common.entity.PageInfo;
import com.frank.common.enums.IsDeletedFlagEnum;
import com.frank.common.utils.LoginUtil;
import com.frank.practice.api.req.GetPracticeSubjectsReq;
import com.frank.practice.api.vo.*;
import com.frank.practice.entity.*;
import com.frank.practice.entity.dto.CategoryDto;
import com.frank.practice.entity.dto.PracticeSetDto;
import com.frank.practice.entity.dto.PracticeSubjectDto;
import com.frank.practice.entity.po.PrimaryCategoryPo;
import com.frank.practice.entity.po.SubjectPo;
import com.frank.practice.enums.CompleteStatusEnum;
import com.frank.practice.enums.PracticeSetTypeEnum;
import com.frank.practice.enums.PracticeSubjectIsAnswerEnum;
import com.frank.practice.enums.SubjectInfoTypeEnum;
import com.frank.practice.mapper.*;
import com.frank.practice.service.PracticeSetService;
import com.frank.practice.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Frank Zou
 * @version v0.0.1
 * @date 2025/3/18
 * @description
 */
@Service
@Slf4j
public class PracticeSetServiceImpl implements PracticeSetService {

    private final PracticeSetMapper practiceSetMapper;

    private final SubjectCategoryMapper subjectCategoryMapper;

    private final SubjectMappingMapper subjectMappingMapper;

    private final SubjectLabelMapper subjectLabelMapper;

    private final SubjectInfoMapper subjectInfoMapper;

    private final PracticeSetDetailMapper practiceSetDetailMapper;
    private final PracticeDetailMapper practiceDetailMapper;
    private final PracticeInfoMapper practiceInfoMapper;
    private final SubjectRadioMapper subjectRadioMapper;
    private final SubjectMultipleMapper subjectMultipleMapper;
    private final SubjectJudgeMapper subjectJudgeMapper;

    public PracticeSetServiceImpl(
            PracticeSetMapper practiceSetMapper,
            SubjectCategoryMapper subjectCategoryMapper,
            SubjectMappingMapper subjectMappingMapper,
            SubjectLabelMapper subjectLabelMapper,
            SubjectInfoMapper subjectInfoMapper,
            PracticeSetDetailMapper practiceSetDetailMapper, PracticeDetailMapper practiceDetailMapper, PracticeInfoMapper practiceInfoMapper, SubjectRadioMapper subjectRadioMapper, SubjectMultipleMapper subjectMultipleMapper, SubjectJudgeMapper subjectJudgeMapper) {
        this.practiceSetMapper = practiceSetMapper;
        this.subjectCategoryMapper = subjectCategoryMapper;
        this.subjectMappingMapper = subjectMappingMapper;
        this.subjectLabelMapper = subjectLabelMapper;
        this.subjectInfoMapper = subjectInfoMapper;
        this.practiceSetDetailMapper = practiceSetDetailMapper;
        this.practiceDetailMapper = practiceDetailMapper;
        this.practiceInfoMapper = practiceInfoMapper;
        this.subjectRadioMapper = subjectRadioMapper;
        this.subjectMultipleMapper = subjectMultipleMapper;
        this.subjectJudgeMapper = subjectJudgeMapper;
    }

    @Override
    public List<SpecialPracticeVo> getSpecialPracticeContent() {
        List<Integer> subjectTypeList = new LinkedList<>();
        subjectTypeList.add(SubjectInfoTypeEnum.RADIO.getCode());
        subjectTypeList.add(SubjectInfoTypeEnum.MULTIPLE.getCode());
        subjectTypeList.add(SubjectInfoTypeEnum.JUDGE.getCode());
        subjectTypeList.add(SubjectInfoTypeEnum.BRIEF.getCode());
        CategoryDto categoryDto = new CategoryDto();
        categoryDto.setSubjectTypeList(subjectTypeList);
        List<PrimaryCategoryPo> poList = subjectCategoryMapper.getPrimaryCategory(categoryDto);
        return poList.stream().map(primaryCategoryPo -> {
            SpecialPracticeVo specialPracticeVo = new SpecialPracticeVo();
            specialPracticeVo.setPrimaryCategoryId(primaryCategoryPo.getParentId());
            SubjectCategory subjectCategory = subjectCategoryMapper.selectById(primaryCategoryPo.getParentId());
            if (subjectCategory == null) {
                return null;
            }
            specialPracticeVo.setPrimaryCategoryName(subjectCategory.getCategoryName());
            LambdaQueryWrapper<SubjectCategory> categoryQuery = Wrappers.lambdaQuery();
            categoryQuery.eq(SubjectCategory::getCategoryType, 2)
                    .eq(SubjectCategory::getParentId, primaryCategoryPo.getParentId());
            List<SubjectCategory> categoryList = subjectCategoryMapper.selectList(categoryQuery);
            List<SpecialPracticeCategoryVo> specialPracticeCategoryVoList = new ArrayList<>();

            categoryList.forEach(category -> {
                List<SpecialPracticeLabelVo> labelVoList = getLabelVoList(category.getId(), subjectTypeList);
                if (!CollectionUtils.isEmpty(labelVoList)) {
                    SpecialPracticeCategoryVo specialPracticeCategoryVo = new SpecialPracticeCategoryVo();
                    specialPracticeCategoryVo.setCategoryId(category.getId());
                    specialPracticeCategoryVo.setCategoryName(category.getCategoryName());
                    specialPracticeCategoryVo.setLabelList(labelVoList);
                    specialPracticeCategoryVoList.add(specialPracticeCategoryVo);
                }

            });
            specialPracticeVo.setCategoryList(specialPracticeCategoryVoList);
            return specialPracticeVo;
        }).collect(Collectors.toList());
    }

    public List<SpecialPracticeLabelVo> getLabelVoList(Long categoryId, List<Integer> subjectType) {
        List<LabelSubjectCountVo> labelCountVoList = subjectMappingMapper.getLabelSubjectCountList(categoryId, subjectType);
        if (CollectionUtils.isEmpty(labelCountVoList)) {
            return Collections.emptyList();
        }
        return labelCountVoList.stream().map(labelSubjectCountVo -> {
            SpecialPracticeLabelVo labelVo = new SpecialPracticeLabelVo();
            SubjectLabel subjectLabel = subjectLabelMapper.selectById(labelSubjectCountVo.getLabelId());
            labelVo.setLabelId(subjectLabel.getId());
            labelVo.setLabelName(subjectLabel.getLabelName());
            labelVo.setAssembleId(categoryId + "-" + subjectLabel.getId());
            return labelVo;
        }).collect(Collectors.toList());
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public PracticeSetVo addPractice(PracticeSubjectDto practiceSubjectDto) {
        PracticeSetVo setVo = new PracticeSetVo();
        List<PracticeSubjectDetailVo> practiceSubjectList = getPracticeSubjectList(practiceSubjectDto);
        if (CollectionUtils.isEmpty(practiceSubjectList)) {
            return setVo;
        }
        PracticeSet practiceSet = new PracticeSet();
        practiceSet.setSetType(PracticeSetTypeEnum.REAL_TIME.getCode());
        List<String> assembleIdList = practiceSubjectDto.getAssembleIds();
        Set<Long> categoryIdSet = new HashSet<>();
        assembleIdList.forEach(assembleId -> {
            Long category = Long.valueOf(assembleId.split("-")[0]);
            categoryIdSet.add(category);
        });
        // 组装试卷名称
        StringBuilder setName = new StringBuilder();
        int index = 1;
        for (Long categoryId : categoryIdSet) {
            if (index > 2) {
                break;
            }
            SubjectCategory subjectCategory = subjectCategoryMapper.selectById(categoryId);
            setName.append(subjectCategory.getCategoryName()).append("、");
            index++;
        }
        setName.deleteCharAt(setName.length() - 1);
        setName.append(index == 2 ? "专项练习" : "等专项练习");
        practiceSet.setSetName(setName.toString());
        String labelId = assembleIdList.get(0).split("-")[1];
        SubjectLabel subjectLabel = subjectLabelMapper.selectById(labelId);
        practiceSet.setPrimaryCategoryId(Long.valueOf(subjectLabel.getCategoryId()));
        practiceSet.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        practiceSet.setCreatedBy(LoginUtil.getLoginId());
        practiceSet.setCreatedTime(new Date());
        practiceSetMapper.insert(practiceSet);
        Long practiceSetId = practiceSet.getId();

        List<PracticeSetDetail> practiceSetDetailList = new LinkedList<>();
        practiceSubjectList.forEach(practiceSubjectDetail -> {
            PracticeSetDetail practiceSetDetail = new PracticeSetDetail();
            practiceSetDetail.setSetId(practiceSetId);
            practiceSetDetail.setSubjectId(practiceSubjectDetail.getSubjectId());
            practiceSetDetail.setSubjectType(practiceSubjectDetail.getSubjectType());
            practiceSetDetail.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
            practiceSetDetail.setCreatedBy(LoginUtil.getLoginId());
            practiceSetDetail.setCreatedTime(new Date());
            practiceSetDetailList.add(practiceSetDetail);
        });
        practiceSetDetailMapper.insertList(practiceSetDetailList);
        setVo.setSetId(practiceSetId);
        return setVo;
    }

    private List<PracticeSubjectDetailVo> getPracticeSubjectList(PracticeSubjectDto practiceSubjectDto) {
        List<PracticeSubjectDetailVo> practiceSubjectList = new LinkedList<>();
        // 将添加好的题目放入集合中，避免重复添加
        List<Long> excludeSubjectIdList = new LinkedList<>();

        // 设置题目的数量
        int radioSubjectCount = 10, multipleSubjectCount = 6, judgeSubjectCount = 4, totalSubjectCount = 20;
        // 先添加单选题
        practiceSubjectDto.setSubjectCount(radioSubjectCount);
        practiceSubjectDto.setSubjectType(SubjectInfoTypeEnum.RADIO.getCode());
        assembleSubjectList(practiceSubjectDto, practiceSubjectList, excludeSubjectIdList);
        // 先添加多选题
        practiceSubjectDto.setSubjectCount(multipleSubjectCount);
        practiceSubjectDto.setSubjectType(SubjectInfoTypeEnum.MULTIPLE.getCode());
        assembleSubjectList(practiceSubjectDto, practiceSubjectList, excludeSubjectIdList);
        // 先添加判断题
        practiceSubjectDto.setSubjectCount(judgeSubjectCount);
        practiceSubjectDto.setSubjectType(SubjectInfoTypeEnum.JUDGE.getCode());
        assembleSubjectList(practiceSubjectDto, practiceSubjectList, excludeSubjectIdList);

        // 判断试题是否足够，如果还不够，则补充题目
        if (practiceSubjectList.size() == totalSubjectCount) {
            // 这里表示题目已经足够了，不需要补充题目，直接返回题目列表
            return practiceSubjectList;
        }
        // 计算出需要补充多少题目
        Integer remainCount = totalSubjectCount - practiceSubjectList.size();
        practiceSubjectDto.setSubjectCount(remainCount);
        // 默认补充一些简答题目
        practiceSubjectDto.setSubjectType(SubjectInfoTypeEnum.BRIEF.getCode());
        assembleSubjectList(practiceSubjectDto, practiceSubjectList, excludeSubjectIdList);
        return practiceSubjectList;


    }

    /**
     * 组装题目列表
     *
     * @param practiceSubjectDto   试卷信息
     * @param practiceSubjectList  试卷题目列表
     * @param excludeSubjectIdList 已添加的题目 ID 列表
     */
    private void assembleSubjectList(PracticeSubjectDto practiceSubjectDto, List<PracticeSubjectDetailVo> practiceSubjectList, List<Long> excludeSubjectIdList) {
        practiceSubjectDto.setExcludeSubjectIds(excludeSubjectIdList);
        List<SubjectPo> subjectPoList = subjectInfoMapper.getPracticeSubjectList(practiceSubjectDto);
        if (CollectionUtils.isEmpty(subjectPoList)) {
            return;
        }
        subjectPoList.forEach(subjectPo -> {
            PracticeSubjectDetailVo subjectDetailVo = new PracticeSubjectDetailVo();
            subjectDetailVo.setSubjectId(subjectPo.getId());
            subjectDetailVo.setSubjectType(subjectPo.getSubjectType());
            // 将查询出来的题目 ID 放入到 excludeSubjectIdList 中，避免重复添加
            excludeSubjectIdList.add(subjectPo.getId());
            practiceSubjectList.add(subjectDetailVo);
        });
    }


    @Override
    public PracticeSubjectListVo getSubjects(GetPracticeSubjectsReq req) {
        PracticeSubjectListVo result = new PracticeSubjectListVo();
        List<PracticeSubjectDetailVo> practiceSubjectDetailVoList = new LinkedList<>();
        List<PracticeSetDetail> practiceSetDetailList = practiceSetDetailMapper.selectBySetId(req.getSetId());
        if (CollectionUtils.isEmpty(practiceSetDetailList)) {
            return result;
        }
        String loginId = LoginUtil.getLoginId();
        Long practiceId = req.getPracticeId();
        practiceSetDetailList.forEach(practiceSetDetail -> {
            PracticeSubjectDetailVo practiceSubjectDetailVo = new PracticeSubjectDetailVo();
            practiceSubjectDetailVo.setSubjectId(practiceSetDetail.getSubjectId());
            practiceSubjectDetailVo.setSubjectType(practiceSetDetail.getSubjectType());
            if (Objects.nonNull(practiceId)) {
                PracticeDetail practiceDetail = practiceDetailMapper.selectByPracticeIdAndSubjectIdAndUser(practiceId, practiceSetDetail.getSubjectId(), loginId);
                if (Objects.nonNull(practiceDetail)) {
                    practiceSubjectDetailVo.setIsAnswer(PracticeSubjectIsAnswerEnum.ANSWERED.getCode());
                    practiceSubjectDetailVo.setAnswerContent(practiceDetail.getAnswerContent());
                } else {
                    practiceSubjectDetailVo.setIsAnswer(PracticeSubjectIsAnswerEnum.UNANSWERED.getCode());
                }
            }
            practiceSubjectDetailVoList.add(practiceSubjectDetailVo);
        });
        result.setSubjectList(practiceSubjectDetailVoList);
        PracticeSet practiceSet = practiceSetMapper.selectById(req.getSetId());
        result.setTitle(practiceSet.getSetName());
        if (Objects.isNull(practiceId)) {
            Long newPracticeId = insertUnCompletePractice(req.getSetId());
            result.setPracticeId(newPracticeId);
        } else {
            updateUnCompletePractice(practiceId);
            PracticeInfo practiceInfo = practiceInfoMapper.selectById(practiceId);
            result.setTimeUse(practiceInfo.getTimeUse());
            result.setPracticeId(practiceId);
        }
        return result;
    }

    /**
     * 根据 setId 找到对应的试卷，然后插入一条未完成的练习记录
     *
     * @param setId 试卷 ID
     * @return 练习记录 ID
     */
    private Long insertUnCompletePractice(Long setId) {
        PracticeInfo practiceInfo = new PracticeInfo();
        practiceInfo.setSetId(setId);
        practiceInfo.setCompleteStatus(CompleteStatusEnum.NO_COMPLETE.getCode());
        practiceInfo.setTimeUse("00:00:00");
        practiceInfo.setSubmitTime(new Date());
        practiceInfo.setCorrectRate(new BigDecimal("0.00"));
        practiceInfo.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        practiceInfo.setCreatedBy(LoginUtil.getLoginId());
        practiceInfo.setCreatedTime(new Date());
        practiceInfoMapper.insert(practiceInfo);
        return practiceInfo.getId();
    }

    private void updateUnCompletePractice(Long practiceId) {
        PracticeInfo practiceInfo = new PracticeInfo();
        practiceInfo.setId(practiceId);
        practiceInfo.setSubmitTime(new Date());
        practiceInfoMapper.updateById(practiceInfo);
    }


    @Override
    public PracticeSubjectVo getPracticeSubject(PracticeSubjectDto practiceSubjectDto) {
        PracticeSubjectVo result = new PracticeSubjectVo();
        SubjectInfo subjectInfo = subjectInfoMapper.selectById(practiceSubjectDto.getSubjectId());
        result.setSubjectName(subjectInfo.getSubjectName());
        result.setSubjectType(subjectInfo.getSubjectType());
        List<PracticeSubjectOptionVo> optionList = new LinkedList<>();
        if (SubjectInfoTypeEnum.RADIO.getCode().equals(subjectInfo.getSubjectType())) {
            List<SubjectRadio> subjectRadios = subjectRadioMapper.queryBySubjectId(practiceSubjectDto.getSubjectId());
            subjectRadios.forEach(subjectRadio -> {
                PracticeSubjectOptionVo option = new PracticeSubjectOptionVo();
                option.setOptionType(subjectRadio.getOptionType());
                option.setOptionContent(subjectRadio.getOptionContent());
                optionList.add(option);
            });
        } else if (SubjectInfoTypeEnum.MULTIPLE.getCode().equals(subjectInfo.getSubjectType())) {
            List<SubjectMultiple> subjectMultiples = subjectMultipleMapper.queryBySubjectId(practiceSubjectDto.getSubjectId());
            subjectMultiples.forEach(subjectMultiple -> {
                PracticeSubjectOptionVo option = new PracticeSubjectOptionVo();
                option.setOptionType(subjectMultiple.getOptionType());
                option.setOptionContent(subjectMultiple.getOptionContent());
                optionList.add(option);
            });
        }
        result.setOptionList(optionList);
        return result;
    }

    @Override
    public Page<PracticeSetVo> getPreSetContent(PracticeSetDto practiceSetDto) {
        Page<PracticeSet> page = new Page<>(practiceSetDto.getPageInfo().getCurrent(), practiceSetDto.getPageInfo().getPageSize());
        LambdaQueryWrapper<PracticeSet> queryWrapper = new LambdaQueryWrapper<>();
//        queryWrapper.eq(PracticeSet::getSetType, 2);
        if (StringUtils.isNotBlank(practiceSetDto.getSetName())) {
            queryWrapper.like(PracticeSet::getSetName, practiceSetDto.getSetName());
        }
        if (Objects.nonNull(practiceSetDto.getOrderType()) && practiceSetDto.getOrderType() == 0) {
            queryWrapper.orderByDesc(PracticeSet::getSetName);
        }
        if (Objects.nonNull(practiceSetDto.getOrderType()) && practiceSetDto.getOrderType() == 1) {
            queryWrapper.orderByDesc(PracticeSet::getCreatedTime);
        }
        if (Objects.nonNull(practiceSetDto.getOrderType()) && practiceSetDto.getOrderType() == 2) {
            queryWrapper.orderByDesc(PracticeSet::getSetHeat);
        }
        page = practiceSetMapper.selectPage(page, queryWrapper);
        List<PracticeSetVo> list = page.getRecords().stream().map(practiceSet -> {
            PracticeSetVo practiceSetVo = new PracticeSetVo();
            practiceSetVo.setSetId(practiceSet.getId());
            practiceSetVo.setSetName(practiceSet.getSetName());
            practiceSetVo.setSetHeat(practiceSet.getSetHeat());
            practiceSetVo.setSetDesc(practiceSet.getSetDesc());
            return practiceSetVo;
        }).collect(Collectors.toList());
        Page<PracticeSetVo> result = new Page<>();
        result.setTotal(page.getTotal())
                .setSize(page.getSize())
                .setCurrent(page.getCurrent())
                .setRecords(list)
                .setPages(page.getPages());
        return result;
    }

    @Override
    public Page<UnCompletePracticeSetVo> getUnCompletePractice(PageInfo pageInfo) {
        String loginId = LoginUtil.getLoginId();
        Page<PracticeInfo> practiceInfoPage = new Page<>(pageInfo.getCurrent(), pageInfo.getPageSize());
        LambdaQueryWrapper<PracticeInfo> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(PracticeInfo::getCreatedBy, loginId)
                .eq(PracticeInfo::getCompleteStatus, CompleteStatusEnum.NO_COMPLETE.getCode())
                .orderByDesc(PracticeInfo::getSubmitTime);
        practiceInfoPage = practiceInfoMapper.selectPage(practiceInfoPage, queryWrapper);
        Page<UnCompletePracticeSetVo> result = new Page<>();
        List<UnCompletePracticeSetVo> records = practiceInfoPage.getRecords().stream().map(item -> {
            UnCompletePracticeSetVo unCompletePracticeSetVo = new UnCompletePracticeSetVo();
            unCompletePracticeSetVo.setSetId(item.getSetId());
            unCompletePracticeSetVo.setPracticeId(item.getId());
            unCompletePracticeSetVo.setPracticeTime(DateUtils.format(item.getSubmitTime(), "yyyy-MM-dd HH:mm:ss"));
            PracticeSet practiceSet = practiceSetMapper.selectById(item.getSetId());
            unCompletePracticeSetVo.setTitle(practiceSet.getSetName());
            return unCompletePracticeSetVo;
        }).collect(Collectors.toList());
        result.setTotal(practiceInfoPage.getTotal())
                .setSize(practiceInfoPage.getSize())
                .setPages(practiceInfoPage.getPages())
                .setCurrent(practiceInfoPage.getCurrent())
                .setRecords(records);
        return result;
    }
}
